package cn.skycity.gateway.config;

import cn.hutool.core.util.ArrayUtil;
import cn.skycity.framework.core.config.IgnoreProperties;
import cn.skycity.framework.core.model.ResultCode;
import cn.skycity.framework.core.utils.WebFluxResponseUtils;
import cn.skycity.gateway.oauth2.LoginLoseHandler;
import jakarta.annotation.Resource;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.security.authentication.ReactiveAuthenticationManager;
import org.springframework.security.authorization.ReactiveAuthorizationManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.SecurityWebFiltersOrder;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
import org.springframework.security.oauth2.client.web.server.DefaultServerOAuth2AuthorizationRequestResolver;
import org.springframework.security.oauth2.client.web.server.ServerOAuth2AuthorizationRequestResolver;
import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenIntrospector;
import org.springframework.security.oauth2.server.resource.web.server.authentication.ServerBearerTokenAuthenticationConverter;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.authentication.AuthenticationWebFilter;
import org.springframework.security.web.server.authorization.AuthorizationContext;
import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler;
import org.springframework.security.web.server.util.matcher.NegatedServerWebExchangeMatcher;
import org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers;
import reactor.core.publisher.Mono;


/**
 * 资源服务器配置
 */
@EnableWebFluxSecurity
@Configuration
@EnableConfigurationProperties(IgnoreProperties.class)
public class ReactiveWebSecurityConfiguration {
    @Resource
    private ReactiveAuthorizationManager<AuthorizationContext> authManagerHandler;
    @Resource
    private ReactiveAuthenticationManager authenticationManager;
    @Resource
    private IgnoreProperties ignoreProperties;
    /// 登录信息失效处理器
    @Resource
    private LoginLoseHandler loginLoseHandler;
    @Resource
    private ReactiveOpaqueTokenIntrospector reactiveOpaqueTokenIntrospector;

    @Bean
    public SecurityWebFilterChain springWebFilterChain(ServerHttpSecurity http, ServerAccessDeniedHandler accessDeniedHandler) {
        AuthenticationWebFilter authorizationWebFilter = new AuthenticationWebFilter(authenticationManager);
        authorizationWebFilter.setServerAuthenticationConverter(new ServerBearerTokenAuthenticationConverter());
        authorizationWebFilter.setRequiresAuthenticationMatcher(
                new NegatedServerWebExchangeMatcher(ServerWebExchangeMatchers.pathMatchers("/login"))
        );

        http.httpBasic(ServerHttpSecurity.HttpBasicSpec::disable)
                .csrf(ServerHttpSecurity.CsrfSpec::disable)
                .oauth2Login(Customizer.withDefaults())
                .authorizeExchange(authorizeExchangeSpec -> {
                    authorizeExchangeSpec.pathMatchers(ArrayUtil.toArray(ignoreProperties.getUrls(), String.class)).permitAll();
                    authorizeExchangeSpec.anyExchange().access(authManagerHandler);
                }).exceptionHandling(exceptionHandlingSpec ->
                        exceptionHandlingSpec.accessDeniedHandler(accessDeniedHandler)
                                .authenticationEntryPoint(loginLoseHandler)
                )
                .oauth2ResourceServer(a -> a.opaqueToken(b -> b.introspector(reactiveOpaqueTokenIntrospector)))
                .addFilterAt(authorizationWebFilter, SecurityWebFiltersOrder.AUTHENTICATION)
        ;
        return http.build();
    }

    /**
     * OAuth2 Client Authorization Endpoint /oauth2/authoriztion/{clientRegId}
     * 请求解析器扩展实现 - 支持提取query参数redirect_uri，用作后续OAuth2认证完成后网关重定向到该指定redirect_uri。
     * 适用场景：前端应用 -> 网关 -> 网关返回401 -> 前端应用重定向到/oauth2/authorization/{clientRegId}?redirect_uri=http://登录后界面 -> 网关完成OAuth2认证后再重定向回http://登录后界面
     */
    @Bean
    @Primary
    public ServerOAuth2AuthorizationRequestResolver saveRequestServerOAuth2AuthorizationRequestResolver(ReactiveClientRegistrationRepository clientRegistrationRepository) {
        return new DefaultServerOAuth2AuthorizationRequestResolver(clientRegistrationRepository);
    }

    @Bean
    ServerAccessDeniedHandler accessDeniedHandler() {
        return (exchange, denied) -> {
            Mono<Void> mono = Mono.defer(() -> Mono.just(exchange.getResponse()))
                    .flatMap(response -> WebFluxResponseUtils.writeErrorInfo(response, ResultCode.ACCESS_UNAUTHORIZED));
            return mono;
        };
    }

}
